package com.dynamic.mybatis.core.datasource;


import com.dynamic.mybatis.core.parser.NamespaceHelper;
import com.dynamic.mybatis.core.toolkit.BeanUtils;
import com.dynamic.mybatis.core.toolkit.StringUtils;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.io.Closeable;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

public class DynamicMultipleDataSource extends AbstractRoutingDataSource {

    //dataSourceKey , dataSource
    private Map<Object,DataSource> dataSources;
    //dataSourceKey , attributeKey attributeValue
    private Map<Object,Map<Object,Object>> attributes;

    private Map<Object,DataSourceProperty> properties;


    public DynamicMultipleDataSource() {
        this(null,new HashMap<>());
    }

    public DynamicMultipleDataSource(DataSource defaultDataSource, Map<Object, DataSource> dataSources) {
        super.setDefaultTargetDataSource(defaultDataSource);
        super.setTargetDataSources((Map<Object, Object>) (Object) dataSources);
        super.afterPropertiesSet();
        this.attributes = new HashMap<>();
        this.properties = new HashMap<>();
        this.dataSources = dataSources;
    }

    public void set(DataSource defaultDataSource, Map<Object, DataSource> dataSources) {
        super.setDefaultTargetDataSource(defaultDataSource);
        super.setTargetDataSources((Map<Object, Object>) (Object) dataSources);
        super.afterPropertiesSet();
        this.attributes = new HashMap<>();
        this.properties = new HashMap<>();
        this.dataSources = dataSources;
    }

    public boolean hasDataSource(String dataSourceKey){
        return  this.dataSources.containsKey(dataSourceKey);
    }

    public void setMainDataSource(String dataSourceKey){
        DataSource mainDataSource = null;
        for(Map.Entry<Object,DataSource> entry :  this.dataSources.entrySet()){
            if(Objects.equals(entry.getKey(),dataSourceKey)){
                mainDataSource = entry.getValue();
            }
        }
        if(mainDataSource != null){
            this.set(mainDataSource,this.dataSources);
        }
    }

    /**
    　* @Description: key 数据源key, vlaue 数据源
    　*/
    public void set(Map<Object, DataSource> dataSources) {
        if (dataSources == null || dataSources.size() < 1) {
            return;
        }
        close(this.dataSources);
        this.dataSources = dataSources;
        super.setTargetDataSources((Map<Object, Object>) (Object)dataSources);
        super.afterPropertiesSet();
    }

    public Map<Object, DataSource> getDataSources(){
        return this.dataSources;
    }

    /**
    　* @Description: key 数据源key, value 数据源对应的扩展属性
    　*/
    public void setAttributes(Map<Object,Map<Object,Object>> attributes){
       this.attributes.putAll(attributes);
    }

    /**
     　* @Description: key 数据源key, value 数据源对应的扩展属性
     　*/
    public void setAttributes(Object dtaSourceKey,Map<Object,Object> attributes){
        if(!this.dataSources.containsKey(dtaSourceKey) || attributes == null){
             return;
        }
        Map<Object,Object> attres =  this.attributes.get(dtaSourceKey);
        if(attres == null){
            attres = new HashMap<>();
            this.attributes.put(dtaSourceKey,attres);
        }
        attres.putAll(attributes);
    }

    /**
     　* @Description: key 数据源key, value 数据源对应的扩展属性
     　*/
    public void setProperty(DataSourceProperty property){
        String key = NamespaceHelper.getIdentity(property.getNamespace(),property.getCode());
        if(!this.dataSources.containsKey(key) || properties == null){
            return;
        }
        if(StringUtils.isNotBlank(property.getUid())){
            Map.Entry<Object,DataSourceProperty> existentEntry = this.properties.entrySet().stream().filter(entry -> Objects.equals(entry.getValue().getUid(),property.getUid())).findFirst().orElse(null);
            if(existentEntry != null){
                String namespace = existentEntry.getValue().getNamespace();
                String code = existentEntry.getValue().getCode();
                BeanUtils.copy(property,existentEntry.getValue());
                existentEntry.getValue().setNamespace(namespace);
                existentEntry.getValue().setCode(code);
                this.properties.put(existentEntry.getKey(),property);
            }
        }else {
            property.setUid(UUID.randomUUID().toString());
        }
        this.properties.put(NamespaceHelper.getIdentity(property.getNamespace(),property.getCode()),property);
    }


    /**
     　* 获取数据源附加属性
     　*/
    public Map<Object,Object> getAttributes(Object key){
      return  this.attributes.get(key);
    }

    public DataSourceProperty getProperty(Object key){
        return  this.properties.get(key);
    }

    public void put(Object key,DataSource dataSource) {
        if (dataSources == null || dataSources.size() < 1) {
             this.dataSources = new HashMap<>();
        }
        this.dataSources.put(key,dataSource);
        set(this.dataSources);
    }

    public void remove(Object key) {
        if (dataSources == null || dataSources.size() < 1) {
           return;
        }
        close(key);
        this.dataSources.remove(key);
        set(this.dataSources);
        this.attributes.remove(key);
    }

    private void close(Map<Object,DataSource> dataSources){
        //被重置数据源和重置数据源属于同一对象时，不进行关闭
        if(dataSources == null || dataSources.size() < 1  || (this.dataSources != null && this.dataSources == dataSources)){
            return;
        }
        dataSources.forEach((key,value) ->{
            DataSource dataSource1 = this.resolveSpecifiedDataSource(dataSources.get(key));
            DataSource dataSource2 = this.resolveSpecifiedDataSource(this.dataSources.get(key));
            if(dataSource1 != null && dataSource2 != null && dataSource1 != dataSource2){
                close(key);
            }
        });
    }

    private void close(Object key){
        DataSource dataSource = this.resolveSpecifiedDataSource(this.dataSources.get(key));
        if(dataSource != null && dataSource instanceof Closeable){
            try {
                ((Closeable)dataSource).close();
            } catch (IOException e) {
                throw new RuntimeException("dataSource close is error."+e,e);
            }
        }
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.get();
    }


}
